/*
 * Copyright (c) 2010, Johan T. Larsson All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Mercenary Commander project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *      
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JOHAN T. LARSSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package games.TBSGame;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Point;

public class Unit implements Comparable<Unit>, Serializable {
	
	private static final long serialVersionUID = 1L;
	
	// Speed is the number of ticks it takes to move one square
	public final static int STATIONARY = 100;
	public final static int VERY_SLOW = 12;
	public final static int SLOW = 6;
	public final static int MEDIUM_FAST = 4;
	public final static int FAST = 3;
	public final static int VERY_FAST = 2;
	public final static int LIGHTNING_FAST = 1;
	
	public final static int GUARDING = 0;
	public final static int OUT_OF_ORDERS = 1;
	public final static int MOVING = 2;
	public final static int BOMBARDING = 3;
	public final static int DEAD = 4;
	
	public final static int INFANTRY = 0;
	public final static int TANK = 1;
	public final static int ARTILLERY = 2;
	public final static int ROCKETARTILLERY = 3;
	
	public int Slowness = STATIONARY;
	public int TicksSinceLastMove = 0;
	public ArrayList<Point> OrderPath = new ArrayList<Point>();
	public int OrderProgress = 0;
	
	public int PosX = 0;
	public int PosY = 0;
	
	public int VisionRange = 1;
	
	public int State = GUARDING;
	
	public int Type = INFANTRY;
	public String Designation = "Unit";
	
	public float HealthPercentage = 100;
	public int Range = 1;
	public int Attack = 1;
	public int Defense = 1;
	
	public int Team = 0;
	
	private FloatBuffer HealthCoords;

	transient private ResourceManager ResourceManager = games.TBSGame.ResourceManager.Singleton;
	private float TexAlignX = 0;
	private float TexAlignY = 0;
	FloatBuffer TexCoords;
	transient private BattleWorld World;
	transient private int ArrowTexture = 0;
	transient private int RedArrowTexture = 0;
	transient private int BombardTexture = 0;
	
	public Unit(BattleWorld world)
	{
		World = world;
	}
	
	public Unit(int x, int y, int team, BattleWorld world) {
		PosX = x;
		PosY = y;
		
		Team = team;
		
		World = world;
	}
	
	public static Unit CreateUnit(int type, int x, int y, int team, BattleWorld world)
	{
		switch(type)
		{
		case INFANTRY:
			return CreateInfantry(x, y, team, world);
		case TANK:
			return CreateTank(x, y, team, world);
		case ARTILLERY:
			return CreateArtillery(x, y, team, world);
		case ROCKETARTILLERY:
			return CreateLauncher(x, y, team, world);
		default:
			return CreateInfantry(x, y, team, world);
		}
	}

	public static Unit CreateTank(int x, int y, int team, BattleWorld world)
	{
		Unit tank = new Unit(x, y, team, world);
		tank.Type = TANK;
		tank.Designation = "Tank";
		
		tank.Slowness = MEDIUM_FAST;
		
		tank.Attack = 100;
		tank.Defense = 50;
		tank.VisionRange = 2;

		tank.TexAlignX = 2f/8f;
		tank.LoadCoords();
		return tank;
	}
	
	public static Unit CreateInfantry(int x, int y, int team, BattleWorld world)
	{
		Unit soldier = new Unit(x, y, team, world);
		soldier.Type = INFANTRY;
		soldier.Designation = "Infantry";
		
		soldier.Slowness = SLOW;
		
		soldier.Attack = 30;
		soldier.Defense = 70;
		
		soldier.TexAlignX = 3f/8f;
		soldier.LoadCoords();
		return soldier;
	}
	
	public static Unit CreateLauncher(int x, int y, int team, BattleWorld world)
	{
		Unit launcher = new Unit(x, y, team, world);
		launcher.Type = ROCKETARTILLERY;
		launcher.Designation = "Rocket Artillery";
		
		launcher.Slowness = VERY_SLOW;
		
		launcher.Attack = 60;
		launcher.Defense = 10;
		launcher.Range = 4;
		
		launcher.TexAlignX = 1f/8f;
		launcher.LoadCoords();
		return launcher;
	}
	
	public static Unit CreateArtillery(int x, int y, int team, BattleWorld world)
	{
		Unit launcher = new Unit(x, y, team, world);
		launcher.Type = ARTILLERY;
		launcher.Designation = "Artillery";
		
		launcher.Slowness = VERY_SLOW;
		
		launcher.Attack = 60;
		launcher.Defense = 10;
		launcher.Range = 4;
		
		launcher.LoadCoords();
		
		return launcher;
	}
	
	private void LoadCoords()
	{
		float vertices[] = {
				TexAlignX, TexAlignY + 1f/8f,
				TexAlignX + 1f/8f, TexAlignY + 1f/8f,
				TexAlignX + 1f/8f, TexAlignY,
				TexAlignX, TexAlignY,
		};
		ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4);
	    bb.order(ByteOrder.nativeOrder());
	    TexCoords = bb.asFloatBuffer();
	    TexCoords.put(vertices);
	    TexCoords.position(0);
	}
	
	public void LoadTextures(GL10 gl)
	{
		ArrowTexture = ResourceManager.LoadTexture(gl, R.drawable.pointer_yellow);
		RedArrowTexture = ResourceManager.LoadTexture(gl, R.drawable.pointer_red);
		BombardTexture = ResourceManager.LoadTexture(gl, R.drawable.bombard_mode);
	}
	
	public void UnloadTextures(GL10 gl)
	{
		ResourceManager.DeleteTexture(gl, ArrowTexture);
		ResourceManager.DeleteTexture(gl, RedArrowTexture);
		ResourceManager.DeleteTexture(gl, BombardTexture);
		
		ArrowTexture = 0;
		RedArrowTexture = 0;
		BombardTexture = 0;
	}
	
	//TODO: Make vertices static?
	public void InitGraphics(GL10 gl) {
		LoadTextures(gl);
		
	    float hVertices[] = {
				0.0f, 0.1f, 0,
				0.5f, 0.1f, 0,
				0.5f, 0.0f, 0,
				0.0f, 0.0f, 0};
		
		ByteBuffer hbb = ByteBuffer.allocateDirect(hVertices.length * 4);
	    hbb.order(ByteOrder.nativeOrder());
	    HealthCoords = hbb.asFloatBuffer();
	    HealthCoords.put(hVertices);
	    HealthCoords.position(0);
	}
	
	//Unload textures, because we can't guarantee the texture label
	//is still correct - and we can't load new textures yet.
	public void onSurfaceCreated(GL10 gl)
	{
		UnloadTextures(gl);
	}
	
	public void Render(GL10 gl)
	{
		if(ArrowTexture == 0)
			InitGraphics(gl);

		gl.glPushMatrix();
        
		if(PosY % 2 == 0)
			gl.glTranslatef(PosX, PosY, 0);
		else
			gl.glTranslatef(PosX + 0.5f, PosY, 0);
		
		RenderAtOrigo(gl);
		RenderHealth(gl);
        
        gl.glPopMatrix();
	}
	
	public void RenderAtOrigo(GL10 gl)
	{
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ResourceManager.SquareCoords);
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, TexCoords);
        gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 4);
	}
	
	public void RenderHealth(GL10 gl)
	{
		gl.glPushMatrix();
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisable(GL10.GL_TEXTURE_2D);
		
		gl.glTranslatef(0.25f, 0, 0);
		gl.glColor4f(0f, 0f, 0f, 1f);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, HealthCoords);
		gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 4);
		
		gl.glTranslatef(0, 0.01f, 0);
		gl.glScalef(HealthPercentage/100.0f, 0.8f, 1.0f);
		gl.glColor4f(1f, 0f, 0f, 1f);
		gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 4);
		
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glPopMatrix();
	}
	
	public void RenderOrders(GL10 gl)
	{
		if(ArrowTexture == 0 || BombardTexture == 0 || RedArrowTexture == 0)
			InitGraphics(gl);
        
		if(State == MOVING)
		{
			Iterator<Point> it = OrderPath.iterator();
			Point p;
			Point fromP = new Point(PosX, PosY);
			int distance = 0;
			int distanceRed = 12/Slowness;
			while(it.hasNext())
			{
				p = it.next();
				gl.glPushMatrix();
		        
				if(fromP.y % 2 == 0)
				{
					gl.glTranslatef(fromP.x, fromP.y, 0);
				}
				else
				{
					gl.glTranslatef(fromP.x + 0.5f, fromP.y, 0);
				}
				
				gl.glTranslatef(0.5f, 0.5f, 0);
				gl.glRotatef((float)OrderDirection(fromP.x, fromP.y, p.x, p.y)*60 + 30, 0, 0, -1);
				gl.glTranslatef(-0.5f, 0.0f, 0);
				//gl.glScalef(0.5f, 0.5f, 1);
				//gl.glTranslatef(-0.2f, 0.3f, 0);
				if(distance < distanceRed)
					gl.glBindTexture(GL10.GL_TEXTURE_2D, ArrowTexture);
				else
					gl.glBindTexture(GL10.GL_TEXTURE_2D, RedArrowTexture);
		        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ResourceManager.SquareCoords);
		        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, ResourceManager.SquareTexCoords);
				gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 4);
				
				gl.glPopMatrix();
				
				fromP = p;
				distance++;
			}
		}
		else if(State == BOMBARDING)
		{
			Point target = OrderPath.get(0);
			
			gl.glPushMatrix();
			
			if(target.y % 2 == 0)
			{
				gl.glTranslatef(target.x, target.y, 0);
			}
			else
			{
				gl.glTranslatef(target.x + 0.5f, target.y, 0);
			}
			
			gl.glBindTexture(GL10.GL_TEXTURE_2D, BombardTexture);
	        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ResourceManager.SquareCoords);
	        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, ResourceManager.SquareTexCoords);
			gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 4);
			
			gl.glPopMatrix();
		}
	}
	
	public int OrderDirection(int fromX, int fromY, int x, int y)
	{
		if(y == fromY)
		{
			if(x == fromX - 1)
			{
				return BattleWorld.WEST;
			}
			if(x == fromX + 1)
			{
				return BattleWorld.EAST;
			}
		}
		if(x == fromX)
		{
			if(y == fromY - 1)
			{
				if(fromY % 2 == 0)
					return BattleWorld.SOUTH_EAST;
				else
					return BattleWorld.SOUTH_WEST;
			}
			if(y == fromY + 1)
			{
				if(fromY % 2 == 0)
					return BattleWorld.NORTH_EAST;
				else
					return BattleWorld.NORTH_WEST;
			}
		}
		if(x == fromX - 1)
		{
			if(fromY % 2 == 0)
			{
				if(y == fromY - 1)
				{
					return BattleWorld.SOUTH_WEST;
				}
				if(y == fromY + 1)
				{
					return BattleWorld.NORTH_WEST;
				}
			}
		}
		if(x == fromX + 1)
		{
			if(fromY % 2 != 0)
			{
				if(y == fromY - 1)
				{
					return BattleWorld.SOUTH_EAST;
				}
				if(y == fromY + 1)
				{
					return BattleWorld.NORTH_EAST;
				}
			}
		}
		return 0;
	}
	
	public boolean OrderTo(int x, int y)
	{
		if(State != MOVING)
			OrderPath.clear();

		if(x == PosX && y == PosY)
		{
			State = GUARDING;
			return false;			
		}
		
		if(x >= World.Size || x < 0 || y >= World.Size || y < 0)
		{
			State = GUARDING;
			return false;			
		}
		
		int fromX = PosX;
		int fromY = PosY;
		int pathSize = OrderPath.size(); 
		if(pathSize != 0)
		{
			fromX = OrderPath.get(pathSize - 1).x;
			fromY = OrderPath.get(pathSize - 1).y;
		}
		
		OrderPath.addAll(World.FindPath(fromX, fromY, x, y));
		
		if(OrderPath.size() == 0)
		{
			State = GUARDING;
			return false;			
		}
		State = MOVING;
		return true;
	}
	
	public boolean Bombard(int x, int y) {
		if(x == PosX && y == PosY)
		{
			OrderPath.clear();
			State = GUARDING;
			return false;
		}
		if(World.Distance(PosX, PosY, x, y) <= Range)
		{
			OrderPath.clear();
			OrderPath.add(new Point(x, y));
			State = BOMBARDING;
			return true;
		}
		State = GUARDING;
		return false;
	}

	public int GetAttack()
	{
		if(HealthPercentage > 75)
			return Attack;
		if(HealthPercentage > 50)
			return (int) (Attack*0.75f);
		return (int) (Attack*0.5f);
	}
	
	public int GetDefense()
	{
		if(HealthPercentage > 75)
			return Defense;
		if(HealthPercentage > 50)
			return (int) (Defense*0.75f);
		return (int) (Defense*0.5f);
	}
	
	public int GetSupport()
	{
		if(HealthPercentage > 75)
			return (int) (Attack*0.25f);
		if(HealthPercentage > 50)
			return (int) (Attack*0.10f);
		return 0;
	}

	@Override
	public int compareTo(Unit other) {
		if(Slowness > other.Slowness)
			return 1;
		if(Slowness < other.Slowness)
			return -1;
		return 0;
	}
}